
import os,sys
import SCons.Script
import SCons.Script.Main
from SCons.Script import Environment
from git import Repo
import functools

# 初始化环境, 提供 prj_cfgs 编译配置环境
env_modle = 'gcc'
pack_mn = '{}-env.mpj'.format(env_modle)
if os.path.exists(pack_mn):
    pck_dn = os.getcwd()
    with open(pack_mn) as fr:
        try:
            exec(fr.read())
        except Exception as e:
            print(f'[env] error in {pack_mn}: {e}')
            sys.exit(-1)

# 工程类型
ptyp_library, ptyp_lib_static, ptyp_lib_share, \
    ptyp_exec = range(4)

dtyp_ignore, dtyp_model, dtyp_test, \
    dtyp_normal, dtyp_project = range(5)

# 文件类型
ftyp_normal, ftyp_src, ftyp_header, ftyp_project, \
     ftyp_ignore, ftyp_test = range(6)

dtyp = {
    'i': dtyp_ignore,
    't': dtyp_test,
    'm': dtyp_model,
    'p': dtyp_project
}

ftyp = {
    'end': {
        'i': ftyp_ignore,
        't': ftyp_test
    },
    'ext': {
        '.c': ftyp_src,
        '.cc': ftyp_src,
        '.cpp': ftyp_src,
        '.h': ftyp_header,
        '.hh': ftyp_header,
        '.hpp': ftyp_header,
        '.mpj':ftyp_project
    }
}


_prj_typ = [ptyp_exec]
_dir_typ = dtyp_normal
# 获取文件夹类型
def get_dtyp(fn):
    global dtyp
    le = fn.split('_')
    if len(le) == 1:
        return dtyp_normal
    if le[-1] in dtyp:
        return dtyp[le[-1]]
    else:
        return dtyp_normal

# 获取文件类型
def get_ftyp(fn):
    ls = os.path.splitext(fn)
    le = ls[0].split('_')
    if len(le) != 1 and le[-1] in ftyp['end']:
        return ftyp['end'][le[-1]]
    if ls[-1] in ftyp['ext']:
        return ftyp['ext'][ls[-1]]

    return ftyp_normal

def get_relate_path(pfa, pson):
    if len(pson) < len(pfa):
        return ''
    if pson[:len(pfa)] == pfa:
        return pson[len(pfa):].lstrip('/')
    else:
        return ''

dirs_idx, flags_idx, lib_dirs_idx, \
libs_idx, exes_idx, objs_idx,\
incs_idx = range(7)
def get_dict_key(dict_, keys_):
    d = {}
    for k in keys_:
        if k not in dict_:
            d[k] = keys_[k]
        else:
            d[k] = dict_[k]
            del dict_[k]
    return d
# 合并
def conbin_info(cur_info, tm_info):
    if not (isinstance(cur_info, dict) \
            and isinstance(tm_info, dict)):
        return
    for t in tm_info:
        if t in cur_info:
            if isinstance(t, str):
                cur_info[t] += ' ' +tm_info[t]
            elif isinstance(t, list):
                cur_info[t] += tm_info[t]
            elif isinstance(t, dict):
                cur_info[t].upate(tm_info[t])
            else:
                print('[conbin_info] unkown type')
        else:
            cur_info[t] = tm_info[t]
    pass
mpj_fn = 'scons.mpj'
cur_traverse_pack = {} # 当前工程下所有的包信息

# 处理依赖项等操作
def gen_flags(dep_info):
    traverse_info = [[dep_info, 0]]
    flags = ''
    files = []
    ex_flags = ''
    # 只加第一層的flags
    for t in dep_info:
        m = modules[t][dep_info[t]['version']]
        flags += ' '+ m.get('flags', '')
        ex_flags += ' '+ dep_info[t].get('ex_flags', '')
        files += dep_info[t].get('files', [])
    # 链接文件在最后, 所有递归依赖的包的链接的操作 dep_info[-L, -l] 都需要汇总
    content_pack = []
    e_flags = ''
    et_flags = []
    while len(traverse_info) > 0:
        e = traverse_info[-1]
        if len(e[0]) == 0:
            traverse_info.pop()
            continue
        for i, t  in zip(range(e[1],len(e[0])), list(e[0])[e[1]:]):
            if t in content_pack: continue
            content_pack.append(t)
            m = modules[t][e[0][t]['version']]
            e_flags += m.get('flags','') 
            if 'dependent' not in m: continue
            is_add_dep = False
            dep_ex = {}
            for mt in m['dependent']:
                if mt in content_pack: continue
                dep_ver = 'default' if 'version' not in m['dependent'][mt] else m['dependent'][mt]['version']
                e[1] = i+1
                dep_ex[mt] = {'version':dep_ver}
                is_add_dep = True
        if is_add_dep:
            traverse_info.append([dep_ex,0])
            continue
        traverse_info.pop()
        for e in e_flags.split():
            if len(e) > 2 and e[:2] in ['-l', '-L'] and e not in et_flags:
                et_flags.append(e)
                
    return {'flags':flags, 'files':files, 'ex_flags':' '.join(et_flags)+f' {ex_flags}'}

#
cur_run_pack = [] # 防止 无限递归
def init_module_build(dependents, tools_info=prj_cfgs):
    dep_info = {}
    
    def run_time(func): # func为要修饰的函数对象
        def warp(*args, **kwargs):
            info = gen_flags(dep_info)
            if 'tools_info' in kwargs:
                temp = func(info, *args, **kwargs) # 执行函数
            else:
                temp = func(info, tools_info=tools_info, *args, **kwargs)
            temp.update({'ex_flags':info.get('ex_flags', '')})
            return temp
        return warp
    if modules is None or len(modules) ==0:
        return run_time
    for d in dependents:
        #  获取依赖按本信息
        dep_v =  dependents[d]['version'] \
                if isinstance(dependents[d], dict) and 'version' in  dependents[d] \
                else  'default' if 'default' in modules[d]  else None
        
        if dep_v is None:
            print('none version for module[{}]-version[{}]'.format(d, dep_v))
            sys.exit(-1)

        if d not in modules:
            print('dependent module "{}" not exists!'.format(d))
            sys.exit(-1)
        if d in cur_traverse_pack:
            dep_info[d] = {"version":dep_v}
            continue
        if d in cur_run_pack:
            dep_info[d] = {"version":dep_v}
            continue
        # 判断结构是否正确
        if not isinstance(dependents,dict):
            print('please insure dependent version')
            sys.exit(-1)
        prj_sn = os.path.join(modules[d][dep_v]['path'],mpj_fn)
        cur_run_pack.append(d)
        if os.path.exists(prj_sn):
            with open(prj_sn) as fr:
                try:
                    exec(fr.read())
                except Exception as e:
                    print(f'[init_module_build] error in {prj_sn}: {e}')
                    sys.exit(-1)
        cur_run_pack.pop()
        if 'module_build' in locals():
            f = locals()['module_build'](modules[d][dep_v],tools_info=tools_info)
            del locals()['module_build']
            cur_traverse_pack[d] = f
            dep_info[d] = {"version":dep_v}
            if f is not None:
                dep_info[d].update(f)
        else:
            dep_info[d] = {"version":dep_v}
            cur_traverse_pack[d] = {}
    return run_time

# 调scons，需要给出 文件地址， 目标地址 和参数。
# 文件地址:  遍历过程
# 目标地址:  工程指定， 默认当前目录下， 工程中间文件在 obj_i 下
# 参数: scons.mpj 文件中
def get_defualt_module(mod_dir='.'):
    # 生成中间文件
    mpj_build(os.getcwd())
    info_n = 6
    objs = []
    for i in range(info_n): objs.append([])

    _dir, _ts, _sub = mod_dir, [], True
    while True:
        if _sub:
            # 遍历目录
            _ret = mpj_build(_dir)
            for i in range(info_n):
                objs[i]+=_ret[i+1]
            idx, _sub =  0, False

        elif len(_ts) > 0:
            # 递归返回
            _dir, idx, _ret = _ts.pop()
        else:
            # 完成退出
            break

        for i,f in zip(range(idx, len(_ret[dirs_idx])),list(_ret[dirs_idx])[idx:]):
            if _ret[dirs_idx][f] != dtyp_ignore:
                _ts.append([_dir, i+1, _ret])
                _dir, _sub = f, True
                break

# 编译
def mpj_build(prj_dn):
    info_keys = {'prj_cfgs':{},'dprj_type':'normal', 'expend_dirs':True, 'expend_files': True}
    if not os.path.isdir(prj_dn):
        return None
    pck_fn = os.path.join(prj_dn, mpj_fn)
    pck_dn = prj_dn
    if os.path.exists(pck_fn):
        with open(pck_fn) as fr:
            exec(fr.read())
        if 'module_build' in locals():
            locals()['module_build'](pck_dn)

def update_model(modules_c, module_p):
    for k1 in module_p:
        for k2 in module_p[k1]:
            if k1 not in modules_c:modules_c[k1] = {}
            if k2 not in modules_c[k1]:
                modules_c[k1][k2] = module_p[k1][k2]
            else:
                s = '{}'.format(k1) if k2 == '' else '{}-{}'.format(k1,k2)
                print(f"{s} is in modules, ignore { module_p[k1][k2]}")

# 遍历工程所有目录,寻找模块
def mpj_find(prj_dn):
    info_keys = {'expend_dirs':True,'dprj_type':'normal'}
    prj_models = {} # 模块名称-> 版本号(模块last)-> 模块参数
    _dir, _ts, _sub = prj_dn, [], True
    while True:
        if _sub:
            # 遍历目录, 判断是否是模块
            _ret = os.listdir(_dir)
            idx, _sub =  0, False

        elif len(_ts) > 0:
            # 递归返回
            _dir, idx, _ret = _ts.pop()
        else:
            # 完成退出
            break
        for i,f in zip(range(idx, len(_ret)),_ret[idx:]):
            cur_f = os.path.join(_dir,f)

            if os.path.isdir(cur_f):
                if f[0] == '.': continue
                prj_sn = os.path.join(cur_f, mpj_fn)
                if os.path.exists(prj_sn):
                    with open(prj_sn) as fr:
                        try:
                            exec(fr.read())
                        except Exception as e:
                            print(f'[mpj_find] error in {prj_sn}: {e}')
                            sys.exit(-1)

                info = get_dict_key(locals(), info_keys)
                if (info['dprj_type'] == 'normal' and get_dtyp(f)==dtyp_ignore) \
                    or info['dprj_type'] == 'ignore':
                    if 'modules_info' in locals():
                        del locals()['modules_info']
                    continue
                if 'modules_info' in locals():
                    mflags = locals()['module_flags'] if 'module_flags' in locals() else ''
                    t = locals()['modules_info'](cur_f, mflags, prj_cfgs)
                    del locals()['modules_info']
                    update_model(prj_models, t)
                    for k in t:
                        if not isinstance(t[k], dict) and len(t[k]) != 1:
                            continue
                        for ke in t[k]:
                            if not isinstance(t[k][ke], dict) and len(t[k]) != 1:
                                continue
                            t[k][ke]['dependent'] = locals()['dependent_packs'] if 'dependent_packs' in locals() else {}

                if info['expend_dirs']:
                    _ts.append([_dir, i+1, _ret])
                    _dir, _sub = cur_f, True
                    break
    return prj_models

modules = {}
modules = mpj_find(os.getcwd())
print('--:local modules:--')
for m in modules:
    print('  > {}'.format(m))
print('-------------------')
mpj_build(os.getcwd())
